package com.earthea.scaffold.system.service;

import cn.hutool.cache.impl.LRUCache;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.earthea.cache.CacheService;
import com.earthea.scaffold.common.config.security.context.AuthenticationContextHolder;
import com.earthea.scaffold.common.entity.BaseDataRsp;
import com.earthea.scaffold.common.entity.BaseRsp;
import com.earthea.scaffold.common.entity.PageResult;
import com.earthea.scaffold.common.enums.BusinessTypeEnum;
import com.earthea.scaffold.common.enums.CacheEnum;
import com.earthea.scaffold.common.enums.HttpCodeEnum;
import com.earthea.scaffold.common.exception.CaptchaException;
import com.earthea.scaffold.common.exception.ServiceException;
import com.earthea.scaffold.common.service.BaseService;
import com.earthea.scaffold.common.util.SecurityUtils;
import com.earthea.scaffold.domain.LoginUser;
import com.earthea.scaffold.domain.SysFile;
import com.earthea.scaffold.domain.User;
import com.earthea.scaffold.mapper.UserMapper;
import com.earthea.scaffold.system.entity.request.LoginReq;
import com.earthea.scaffold.system.entity.request.RegisterReq;
import com.earthea.scaffold.system.entity.request.UserReq;
import com.earthea.scaffold.system.entity.response.LoginRsp;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.Optional;

/**
 * 用户服务类
 */
@Slf4j
@Service
public class UserService extends BaseService<User, UserMapper> {
    @Value("${app.captcha.enabled:true}")
    private boolean captchaEnabled;

    @Autowired
    private BCryptPasswordEncoder encoder;

    @Resource
    private AuthenticationManager authenticationManager;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private CacheService cacheService;

    @Autowired
    private SysFileService sysFileService;

    private static final LRUCache<Long, User> USER_ID_NAME_CACHE = new LRUCache<>(1000, 86400000);

    @PostConstruct
    public void init() {
        mapper.selectPage(new UserReq(1, 1000)).getRecords().forEach(user -> USER_ID_NAME_CACHE.put(user.getUserId(), user));
    }

    /**
     * 注册用户
     *
     * @param registerReq 请求体
     * @return 结果
     */
    @Transactional
    public BaseDataRsp<User> register(RegisterReq registerReq) {
        String username = registerReq.getUsername();
        String phone = registerReq.getPhone();
        String email = registerReq.getEmail();
        String password = registerReq.getPassword();
        User user = new User();
        user.setUsername(username);
        user.setPhone(phone);
        user.setEmail(email);
        user.setSex(registerReq.getSex());

        // 验证码开关
        if (captchaEnabled) {
            validateCaptcha(registerReq.getCode(), registerReq.getUuid());
        }
        BaseDataRsp<User> rsp = new BaseDataRsp<>(HttpCodeEnum.FAIL);
        if (existUserNameUnique(username)) {
            rsp.setMsg("保存用户'" + username + "'失败，注册用户名已存在");
            return rsp;
        } else if (existPhoneUnique(phone)) {
            rsp.setMsg("保存用户'" + phone + "'失败，注册手机号已存在");
            return rsp;
        } else if (existEmailUnique(email)) {
            rsp.setMsg("保存用户'" + email + "'失败，注册邮箱已存在");
            return rsp;
        } else {
            user.setPassword(encoder.encode(password));
            if (mapper.insert(user) <= 0) {
                rsp.setMsg("注册失败,请联系系统管理人员");
                return rsp;
            }
        }
        rsp.setCode(HttpCodeEnum.SUCCESS.getCode());
        rsp.setMsg("注册用户'" + username + "'成功");
        rsp.setData(user);
        return rsp;
    }

    /**
     * 校验验证码
     *
     * @param code 验证码
     * @param uuid 唯一标识
     */
    private void validateCaptcha(String code, String uuid) {
        String verifyKey = CacheEnum.CAPTCHA_CODE.getCacheKey(uuid);
        String captcha = cacheService.get(verifyKey);
        cacheService.del(verifyKey);
        if (captcha == null) {
            throw new CaptchaException("验证码已失效");
        }
        if (!code.equalsIgnoreCase(captcha)) {
            throw new CaptchaException("验证码错误");
        }
    }

    /**
     * 登录
     *
     * @param loginReq 请求体
     * @return 结果
     */
    public LoginRsp login(LoginReq loginReq) {
        LoginRsp result = BaseRsp.success(LoginRsp.class);
        // 验证码开关
        if (captchaEnabled) {
            validateCaptcha(loginReq.getCode(), loginReq.getUuid());
        }
        // 用户验证
        Authentication authentication;
        try {
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginReq.getLoginName(), loginReq.getPassword());
            AuthenticationContextHolder.setContext(authenticationToken);
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager.authenticate(authenticationToken);
        } catch (Exception e) {
            if (e instanceof BadCredentialsException) {
                throw new UsernameNotFoundException("用户不存在/密码错误");
            } else {
                throw new ServiceException(e);
            }
        } finally {
            AuthenticationContextHolder.clearContext();
        }
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        // 生成token
        String token = tokenService.createToken(loginUser);
        result.setToken(token);
        result.setUserId(loginUser.getUserId());
        // 更新登录信息
        User user = new User();
        user.setUserId(loginUser.getUserId());
        user.setUpdateBy(loginUser.getUserId());
        user.setLoginIp(loginUser.getLoginIp());
        user.setLoginTime(loginUser.getLoginTime());
        updateById(user);
        return result;
    }

    /**
     * 分页查询用户
     *
     * @param req 请求体
     * @return 结果
     */
    public PageResult<User> selectPage(UserReq req) {
        Page<User> page = mapper.selectPage(req);
        return new PageResult<>(page);
    }

    /**
     * 按用户ID查询用户，优先使用缓存
     *
     * @param userId 用户ID
     * @return 用户
     */
    public User selectCacheById(Long userId) {
        if (userId == null) {
            return null;
        }
        User user = USER_ID_NAME_CACHE.get(userId);
        if (user == null) {
            user = Optional.ofNullable(mapper.selectById(userId)).orElse(new User());
            USER_ID_NAME_CACHE.put(userId, user);
        }
        return user;
    }

    /**
     * 按照登录账号查询用户
     *
     * @param loginName 用户名/邮箱/手机号
     * @return 用户
     */
    public User selectByUser(String loginName) {
        return mapper.selectByLoginName(loginName);
    }

    /**
     * 更新用户头像
     *
     * @param userId 用户ID
     * @param file   头像文件
     * @return 结果
     */
    @Transactional
    public BaseDataRsp<SysFile> updateAvatar(Long userId, MultipartFile file) {
        BaseDataRsp<SysFile> sysFileRsp = sysFileService.upload(userId, BusinessTypeEnum.USER_AVATAR, file);
        Long fileId = Optional.of(sysFileRsp).map(BaseDataRsp::getData).map(SysFile::getFileId).orElse(null);
        if (fileId != null) {
            User user = new User();
            user.setUserId(userId);
            user.setAvatarId(fileId);
            user.setUpdateBy(SecurityUtils.getLoginUserId());
            mapper.updateById(user);
        }
        return sysFileRsp;
    }

    /**
     * 是否存在重复的用户名
     *
     * @param username 用户名
     * @return 是否存在
     */
    public boolean existUserNameUnique(String username) {
        return mapper.checkUserNameUnique(username) > 0;
    }

    /**
     * 是否存在重复的手机号
     *
     * @param phone 手机号
     * @return 是否存在
     */
    public boolean existPhoneUnique(String phone) {
        return mapper.checkPhoneUnique(phone) > 0;
    }

    /**
     * 是否存在重复的邮箱
     *
     * @param email 邮箱
     * @return 是否存在
     */
    public boolean existEmailUnique(String email) {
        return mapper.checkEmailUnique(email) > 0;
    }
}